package com.accesshollywood.xmas2011.core;

import static playn.core.PlayN.assetManager;
import static playn.core.PlayN.graphics;

import java.util.ArrayList;

import org.jbox2d.callbacks.ContactImpulse;
import org.jbox2d.callbacks.ContactListener;
import org.jbox2d.callbacks.DebugDraw;
import org.jbox2d.callbacks.QueryCallback;
import org.jbox2d.collision.AABB;
import org.jbox2d.collision.Collision;
import org.jbox2d.collision.Collision.PointState;
import org.jbox2d.collision.Manifold;
import org.jbox2d.collision.WorldManifold;
import org.jbox2d.collision.shapes.PolygonShape;
import org.jbox2d.common.IViewportTransform;
import org.jbox2d.common.OBBViewportTransform;
import org.jbox2d.common.Vec2;
import org.jbox2d.dynamics.Body;
import org.jbox2d.dynamics.BodyDef;
import org.jbox2d.dynamics.BodyType;
import org.jbox2d.dynamics.Fixture;
import org.jbox2d.dynamics.FixtureDef;
import org.jbox2d.dynamics.World;
import org.jbox2d.dynamics.contacts.Contact;
import org.jbox2d.dynamics.joints.MouseJoint;
import org.jbox2d.dynamics.joints.MouseJointDef;

import playn.core.CanvasLayer;
import playn.core.DebugDrawBox2D;
import playn.core.GroupLayer;
import playn.core.Image;
import playn.core.ImageLayer;
import playn.core.Pointer;

public class Master {
	public enum MODE {
		SETUP,
		LOOKING,
		AIMING,
		FIRING,
		DESTROYING
	}

	IViewportTransform	viewportTransform;
	static final float	DPI						= 200.0f;
	static final float	worldScale				= DPI / 2.0f;
	static final float	timeStep				= 1.0f / 30.0f;
	static final int	velocityIterations		= 6;
	static final int	positionIterations		= 2;

	Vec2				gravity;
	boolean				doSleep;
	World				world;

	ArrayList<Body>		bodies;
	Body				groundBody;
	Body				billyBody;

	CanvasLayer			debugCanvas;
	boolean				doDebug					= false;

	ArrayList<Body>		mouseables;
	MouseJoint			mouseJoint;
	MouseJoint			slingshotJoint;
	Vec2				slingshotPos;
	MODE				mode;
	private float		oldSlingshotForceMag	= 0;
	private float		slingshotPower			= 600;

	private int		launchPlatform = -300;

	GroupLayer			foreground;
	GroupLayer			background;

	public Master() {
		mode = MODE.SETUP;
		viewportTransform = new OBBViewportTransform();
		viewportTransform.setYFlip(true);
		graphics().setSize(600, 400);
		gravity = new Vec2(0.0f, -10.0f);
		doSleep = true;

		world = new World(gravity, doSleep);

		bodies = new ArrayList<Body>();
		mouseables = new ArrayList<Body>();

		background = graphics().createGroupLayer();
		foreground = graphics().createGroupLayer();

		graphics().rootLayer().add(background);
		graphics().rootLayer().add(foreground);

		createBG();
		bodies.add(createGround());

		bodies.add(createGift(600, -325));
		bodies.add(createGift(550, -325));
		bodies.add(createGift(700, -300));
		bodies.add(createGift(600, -225));
		bodies.add(createGift(550, -225));
		bodies.add(createGift(700, -200));

		bodies.add(createBilly(150, launchPlatform + 50));

		createSlingshot(150, -250, billyBody);

		createDebug();

		world.setContactListener(new MyContactListener());
		mode = MODE.LOOKING;
	}

	public void createSlingshot(int X, int Y, Body shooter) {
		slingshotPos = new Vec2(X / Master.worldScale, Y / Master.worldScale);

		MouseJointDef slingshotJointDef = new MouseJointDef();
		slingshotJointDef.bodyA = groundBody;
		slingshotJointDef.bodyB = shooter;
		Vec2 startPos = shooter.getWorldCenter();
		slingshotJointDef.target.set(startPos);
		slingshotJointDef.collideConnected = true;
		slingshotJointDef.maxForce = slingshotPower * shooter.getMass();
		slingshotJoint = (MouseJoint) world.createJoint(slingshotJointDef);
	}

	public void createBG() {
		Image skinImage = assetManager().getImage("images/bg.png");
		ImageLayer skin = graphics().createImageLayer(skinImage);
		background.add(skin);

	}

	public Body createGround() {
		BodyDef bodyDef = new BodyDef();

		bodyDef.userData = new Billboard("images/ground.png", foreground);
		bodyDef.position.set(400 / Master.worldScale, -400 / Master.worldScale);

		groundBody = world.createBody(bodyDef);
		 ((Billboard) groundBody.getUserData()).setVisible(false);
		 ((Billboard) groundBody.getUserData()).parentBody = groundBody;
		 ((Billboard) groundBody.getUserData()).maxHitPoints = -1;
		 ((Billboard) groundBody.getUserData()).currHitPoints = -1;

		 PolygonShape box = new PolygonShape();
		 box.setAsBox(800 / DPI, 75 / DPI);

		 groundBody.createFixture(box, 0.0f);

		//GroundShape shape = new GroundShape(800 / Master.worldScale, 400 / Master.worldScale, launchPlatform / Master.worldScale, 7);
		//groundBody.createFixture(shape, 0.0f);

		return groundBody;
	}

	public Body createGift(int X, int Y) {
		BodyDef bodyDef = new BodyDef();
		bodyDef.type = BodyType.DYNAMIC;
		bodyDef.position.set(X / Master.worldScale, Y / Master.worldScale);
		bodyDef.userData = new Billboard("images/gift.png", foreground);
		Body body = world.createBody(bodyDef);
		((Billboard) body.getUserData()).parentBody = body;
		((Billboard) body.getUserData()).maxHitPoints = 80;
		((Billboard) body.getUserData()).currHitPoints = ((Billboard) body.getUserData()).maxHitPoints;
		PolygonShape box = new PolygonShape();
		box.setAsBox(65 / DPI, 65 / DPI);

		FixtureDef fixtureDef = new FixtureDef();
		fixtureDef.shape = box;
		fixtureDef.density = 0.1f;

		Fixture fixture = body.createFixture(fixtureDef);

		return body;
	}

	public Body createBilly(int X, int Y) {// 48x65
		BodyDef bodyDef = new BodyDef();
		bodyDef.type = BodyType.DYNAMIC;
		bodyDef.position.set(X / Master.worldScale, Y / Master.worldScale);
		bodyDef.angle = 0.0f;
		bodyDef.userData = new Billboard("images/billyHead_small2.png", foreground);

		billyBody = world.createBody(bodyDef);

		((Billboard) billyBody.getUserData()).parentBody = billyBody;
		((Billboard) billyBody.getUserData()).maxHitPoints = -1;
		((Billboard) billyBody.getUserData()).currHitPoints = -1;

		EllipseShape shape = new EllipseShape(48 / DPI, 65 / DPI, 12);

		FixtureDef fixtureDef = new FixtureDef();
		fixtureDef.shape = shape;
		fixtureDef.density = 100.0f;
		fixtureDef.friction = 0.3f;

		billyBody.createFixture(fixtureDef);

		mouseables.add(billyBody);

		return billyBody;
	}

	public void createDebug() {
		if (doDebug) {
			debugCanvas = graphics().createCanvasLayer(800, 400);
			DebugDrawBox2D myDebug = new DebugDrawBox2D();
			myDebug.setFlags(DebugDraw.e_shapeBit);
			myDebug.setFlipY(true);
			myDebug.setCanvas(debugCanvas);
			myDebug.setCameraScale(Master.worldScale);
			graphics().rootLayer().add(debugCanvas);
			world.setDebugDraw(myDebug);
		}
	}

	public void update() {
		world.step(timeStep, velocityIterations, positionIterations);
		float foregroundX = Math.min(400, Math.max(0, billyBody.getPosition().x * Master.worldScale - 150));
		System.out.println(foregroundX);
		foreground.setTranslation(-foregroundX, 0);
		background.setTranslation(-foregroundX/2.0f, 0);

		if (mode == MODE.FIRING) {
			checkSlingshotForRelease();
		}

		checkForDestruction();
	}

	public void paint() {
		for (Body currBody : bodies) {
			((Billboard) currBody.getUserData()).pinToBody();
		}

		if (doDebug) {
			debugCanvas.canvas().clear();
			world.drawDebugData();
		}
	}

	public void checkSlingshotForRelease() {
		Vec2 slingshotForce = new Vec2();
		slingshotJoint.getReactionForce(1, slingshotForce);

		float slingshotForceMag = slingshotForce.length();
		if (slingshotForceMag < oldSlingshotForceMag) {
			mode = MODE.DESTROYING;
			world.destroyJoint(slingshotJoint);
		}

		oldSlingshotForceMag = slingshotForceMag;

	}

	public void checkForDestruction() {
		for (int i = bodies.size() - 1; i >= 0; i--) {
			Body currBody = bodies.get(i);
			if (((Billboard) currBody.getUserData()).isBroken()) {
				((Billboard) currBody.getUserData()).destroy();
				bodies.remove(currBody);
				world.destroyBody(currBody);
			}
		}
	}

	public class MyContactListener implements ContactListener {

		@Override
		public void beginContact(Contact contact) {
			// TODO Auto-generated method stub

		}

		@Override
		public void endContact(Contact contact) {
			// TODO Auto-generated method stub

		}

		@Override
		public void preSolve(Contact contact, Manifold oldManifold) {
			WorldManifold worldManifold = new WorldManifold();

			contact.getWorldManifold(worldManifold);

			PointState[] state1 = new PointState[2];
			PointState[] state2 = new PointState[2];

			Collision.getPointStates(state1, state2, oldManifold, contact.getManifold());

			if (state2[0] == PointState.ADD_STATE) {
				Body bodyA = contact.getFixtureA().getBody();
				Body bodyB = contact.getFixtureB().getBody();
				Billboard bbA = (Billboard) bodyA.getUserData();
				Billboard bbB = (Billboard) bodyB.getUserData();

				Vec2 point = worldManifold.points[0];

				Vec2 vA = bodyA.getLinearVelocityFromLocalPoint(point);
				Vec2 vB = bodyB.getLinearVelocityFromLocalPoint(point);

				float approachVelocity = Math.abs(Vec2.dot(vB.sub(vA), worldManifold.normal));

				if (approachVelocity > 1.0f) {
					if (bbA.isBreakable()) {
						bbA.currHitPoints -= approachVelocity;
					}

					if (bbB.isBreakable()) {
						bbB.currHitPoints -= approachVelocity;
					}
				}
			}
		}

		@Override
		public void postSolve(Contact contact, ContactImpulse impulse) {
			// TODO Auto-generated method stub

		}
	}

	public class MyPointerListener extends Pointer.Adapter {
		AABB	aabb;

		public MyPointerListener() {
			aabb = new AABB();
		}

		@Override
		public void onPointerStart(Pointer.Event event) {
			mode = MODE.AIMING;
			Vec2 screenVecLo = new Vec2((event.x() - 1) / worldScale, (event.y() - 1) / worldScale);
			Vec2 screenVecHi = new Vec2((event.x() + 1) / worldScale, (event.y() + 1) / worldScale);
			Vec2 worldVecLo = new Vec2();
			Vec2 worldVecHi = new Vec2();
			viewportTransform.getScreenToWorld(screenVecLo, worldVecLo);
			viewportTransform.getScreenToWorld(screenVecHi, worldVecHi);

			for (Body currBody : bodies) {
				AABB currAABB = currBody.getFixtureList().getAABB();
			}

			aabb.lowerBound.set(worldVecLo.x, worldVecLo.y);
			aabb.upperBound.set(worldVecHi.x, worldVecHi.y);
			world.queryAABB(new MyQueryCallback(), aabb);
		}

		@Override
		public void onPointerEnd(Pointer.Event event) {
			mode = MODE.FIRING;
			world.destroyJoint(mouseJoint);
		}

		public void onPointerDrag(Pointer.Event event) {
			if (mode == MODE.AIMING) {
				Vec2 currMousePos = new Vec2(event.x() / worldScale, event.y() / worldScale);
				Vec2 worldMousePos = new Vec2();
				viewportTransform.getScreenToWorld(currMousePos, worldMousePos);
				mouseJoint.setTarget(worldMousePos);
			}
		}
	}

	public class MyQueryCallback implements QueryCallback {

		@Override
		public boolean reportFixture(Fixture fixture) {
			Body body = fixture.getBody();
			if (mouseables.contains(body)) {
				MouseJointDef mouseJointDef = new MouseJointDef();
				mouseJointDef.bodyA = groundBody;
				mouseJointDef.bodyB = body;
				Vec2 startPos = body.getWorldCenter();
				mouseJointDef.target.set(startPos);
				mouseJointDef.collideConnected = true;
				mouseJointDef.maxForce = slingshotPower * body.getMass();
				mouseJoint = (MouseJoint) world.createJoint(mouseJointDef);

				return false;
			} else {
				return true;
			}
		}
	}

}
